import random
import time
import math
import copy
PI = 3.1415926
NUMIND = 25
FUN = 1
crossPro = 0.9
mutaPro = 0.1
up=10
low=-10

def check_border(x):
    if x>up:
        return up
    if x<low:
        return low
    return x

# 1.实数编码 2.交叉近亲回避 3.两点变异 4.多目标优化
class Indivi():
    def __init__(self):
        self.fitness = 0.0
        self.x1 = 0.0
        self.x2 = 0.0
        self.upLim = 0.0

class Popu:
    def __init__(self):
        self.indv = [Indivi() for i in range(NUMIND)]
        self.bestInd = Indivi()
        self.generan = 0

pop = Popu()


def ranF(x, y):
    return (((float(random.randint(0, 32767)) + 90.0) * (y - x) / 33956.0) + x)


# 目标函数 y=x·sin(10·PI·x)
def fit(x1,x2, i):
    if i == 1:
        return 3*(x1**2-x2)**2
    else:
        return x1,x2

# 初始化种群
def initPop():
    pop.generan = 0
    for i in range(NUMIND):
        pop.indv[i].x1 = random.uniform(-10, 10)
        pop.indv[i].x2 = random.uniform(-10, 10)

# 转换到[-1,2]
def calFit():
    for i in range(NUMIND):
        pop.indv[i].fitness = fit(pop.indv[i].x1,pop.indv[i].x2, FUN)

# upLim轮盘赌上界
# temp为下一个种群个体的适应值除以总适应值，同时上界为:temp+pop.indv[i].upLim
# 如果种群个体还没到最后一个，但上界已经超过了1.0，就输出错误信息，并推出exit(0)
# 如果没有错误，则最后一个的上界赋值为 1
def calUp():
    Sum = sum([pop.indv[i].fitness for i in range(NUMIND)])
    pop.indv[0].upLim = pop.indv[0].fitness / Sum
    for i in range(NUMIND - 1):
        temp = pop.indv[i + 1].fitness / Sum
        pop.indv[i + 1].upLim = temp + pop.indv[i].upLim
        if pop.indv[i + 1].upLim > 1.0 and (i + 1) < (NUMIND - 1):
            for j in range(NUMIND):
                print(
                    f"generation is: {pop.generan}, fitness = {pop.indv[j].fitness}, UpLim = {pop.indv[j].upLim}\n"
                )
            print(f"\nError,{i+1}'s upLim is greater than 1=============\n")
            exit(0)
    pop.indv[NUMIND - 1].upLim = 1

# 一个已排序的列表 InA 中查找一个值 value 的位置，并返回该位置的索引，LowBo：下限, UpBo：上限
def HalfSear(value, LowBo, UpBo, InA):
    Mid = (LowBo + UpBo) // 2
    if Mid == 0:
        return 0
    if (value <= InA[Mid]) and (value > InA[Mid - 1]):
        return Mid
    else:
        if value >= InA[Mid]:
            return HalfSear(value, Mid, UpBo, InA)
        else:
            return HalfSear(value, LowBo, Mid, InA)

# TemFitn列表存储每个个体在轮盘赌中被选中的概率
# 将pop.indv[NumTemp]设为新个体
def calSub():
    TemFitn = [pop.indv[i].upLim for i in range(NUMIND)]
    for i in range(NUMIND):
        rnd = ranF(0, 1)
        NumTemp = HalfSear(rnd, 0, NUMIND, TemFitn)
        pop.indv[i] = pop.indv[NumTemp]


def select():
    calUp()
    calSub()

# 基因交叉
# random.randint(a, b) 函数可以生成一个在区间 [a, b] 内的随机整数
# range(i, LENIND) 会生成一个范围对象
def swithstr(s1, s2, t1, t2):
    w1 = random.uniform(0.6,0.8)
    w2 = 1-w1
    temp = w1*s1+w2*t1
    t1 = w1 * t1 + w2 * s1
    s1 = temp

    temp = w1*s2+w2*t2
    t2 = w1 * t2 + w2 * s2
    s2 = temp
    #范围检查
    s1 = check_border(s1)
    s2 = check_border(s2)
    t1 = check_border(t1)
    t2 = check_border(t2)
    return s1,s2,t1,t2

#近亲回避
def crossover():
    for i in range(NUMIND // 2):
        if ranF(0, 1) > crossPro:
            pass
        else:
            j = k = 0
            while j == k:  # 直到jk不相等跳出循环
                j = int(ranF(0, NUMIND))
                ranF(0, 1)
                k = int(ranF(0, NUMIND))
            pop.indv[j].x1,pop.indv[j].x2, pop.indv[k].x1, pop.indv[k].x2 = swithstr(pop.indv[j].x1,pop.indv[j].x2, pop.indv[k].x1, pop.indv[k].x2)


#两点变异
def changeM(x1,x2):
    # 将传入的二进制串 ind 中某个位置上的 0 和 1 进行互换
    r1 = random.uniform(-0.1, 0.1)  # 扰动强度
    r2 = random.uniform(-0.1, 0.1)  # 扰动强度
    x1 = check_border(x1+r1+r2)
    x2 = check_border(x2 + r1 + r2)
    return x1,x2


def mutation():
    for i in range(NUMIND):
        if ranF(0, 1) > mutaPro:
            continue
        else:
            pop.indv[i].x1, pop.indv[i].x2 = changeM(pop.indv[i].x1, pop.indv[i].x2)


def max():
    max_fitness = -float("inf")
    max_index = 0
    for i in range(NUMIND):
        if pop.indv[i].fitness > max_fitness:
            max_fitness = pop.indv[i].fitness
            max_index = i
    pop.bestInd = copy.deepcopy(pop.indv[max_index])

if __name__ == "__main__":
    random.seed(int(time.time()) % 9)
    initPop()
    with open("a.txt", "w") as fp:
        # 3.85027
        while pop.generan < 1000:
            calFit()
            max()
            print(f"The generation is {pop.generan}, the best fitness is {pop.bestInd.fitness}")
            fp.write(f"Best Individual: {pop.bestInd.x1,pop.bestInd.x2}, {pop.bestInd.fitness}\n")
            select()
            crossover()
            if pop.generan == 100:
                mutaPro = 0.3
                print("The mutation probability is set to 0.3")
            mutation()
            pop.indv[0] = pop.bestInd
            pop.generan += 1
    print("finished")



